#include "BlockRenderers.h"
#include "Box.h"
#include "RendererBase.h"
#include "IBlockRenderer.h"
#include "ITexture.h"
#include "..\Schematics\Block.h"
#include "..\..\Math\Point2.h"
#include "..\..\Math\Colorb.h"
#include "..\Scripting\RenderScriptEngine.h"

ZipFile::ZipFile() :
	_stream(nullptr),
	_fileStream(nullptr)
{
}

ZipFile::~ZipFile()
{
	Close();
}

wxInputStream &ZipFile::GetStream()
{
	return *_stream;
}

bool ZipFile::IsSet()
{
	return _stream != nullptr;
}

bool ZipFile::Exists(const wxString &file)
{
	return _entries.find(file) != _entries.end();
}

void ZipFile::Open(const wxString &file)
{
	_fileStream = new wxFileInputStream(file);
	_stream = new wxZipInputStream(_fileStream);

	std::auto_ptr<wxZipEntry> entry;

	while (entry.reset(_stream->GetNextEntry()), entry.get() != nullptr)
		_entries[entry.get()->GetName()] = entry;
}

wxFileOffset ZipFile::OpenEntry(const wxString &file)
{
	_stream->CloseEntry();
	auto &entry = _entries[file];
	_stream->OpenEntry(*entry);

	return entry->GetSize();
}

void ZipFile::CloseEntry()
{
	if (_stream != nullptr)
		_stream->CloseEntry();
}

void ZipFile::Close()
{
	CloseEntry();
	delete _stream;
	_entries.clear();
}

TexturePackHandler::TexturePackHandler(BlockRenderers *blocks, const wxString &tp, RendererBase *renderer) :
	_base(renderer),
	_blocks(blocks)
{
	_default.Open(_getDefaultPack());

	if (!tp.empty())
		_tp.Open(tp);
}

TexturePackHandler::~TexturePackHandler()
{
}

void TexturePackHandler::SetPack(const wxString &string)
{
	if (_tp.IsSet())
		_tp.Close();

	if (!string.empty())
		_tp.Open(string);
}

void TexturePackHandler::CreateScaledTexture(std::pair<wxString, ITexture*> &texture)
{
	if (!_default.Exists(texture.first))
	{
		if (wxFileExists(texture.first))
		{
			texture.second->Load(texture.first);
			return;
		}

		texture.second->Copy(_blocks->GetTexture("__unknown__"));
		return;
	}

	_default.OpenEntry(texture.first);
	ITexture *defaultTexture = _base->CreateTexture();
	defaultTexture->Load(_default.GetStream());

	if (!_tp.IsSet() || !_tp.Exists(texture.first))
	{
		texture.second->Copy(defaultTexture);
		delete defaultTexture;
		return;
	}

	_tp.OpenEntry(texture.first);
	ITexture *tpTexture = _base->CreateTexture();
	tpTexture->Load(_tp.GetStream());
	tpTexture->SetScale(tpTexture->GetWidth() / defaultTexture->GetWidth());
		
	texture.second->Copy(tpTexture);

	defaultTexture->Delete();
	delete defaultTexture;
	delete tpTexture;
}

std::shared_ptr<byte> TexturePackHandler::LoadFile(const wxString &str)
{
	ZipFile &file = (!_tp.IsSet() || !_tp.Exists(str)) ? _default : _tp;

	if (!file.Exists(str))
		return nullptr;

	auto size = file.OpenEntry(str);
	auto &stream = file.GetStream();

	std::shared_ptr<byte> ptr(new byte[size]);
	stream.Read(ptr.get(), size);

	return ptr;
}

ITexture *TexturePackHandler::CreateScaledTexture(const wxString &str)
{
	auto f = _blocks->_texturePool.find(str);
	bool isNew = f == _blocks->_texturePool.end();

	std::pair<wxString, ITexture*> p(str, (isNew) ? _base->CreateTexture() : (*f).second);
			
	CreateScaledTexture(p);

	if (isNew)
		_blocks->AddTexture(str, p.second);

	return p.second;
}

ITexture *BlockRenderers::GetTexture(const wxString &string)
{
	auto it = _texturePool.find(string);

	if (it == _texturePool.end())
	{
		auto scaled = _texturePack->CreateScaledTexture(string);

		if (scaled == nullptr)
			return _texturePool["__unknown__"];

		AddTexture(string, scaled);
		return scaled;
	}

	return (*it).second;
}

void BlockRenderers::AddTexture(const wxString &string, ITexture *texture)
{
	_texturePool.insert(std::make_pair(string, texture));
}

void BlockRenderers::AddRender(const int &id, void *renderer)
{
	Renderers.insert(std::make_pair(id, BlockRendererData(_scriptEngine->GetCurrentModule(), renderer, _scriptEngine->ExecuteIsFullBlock(renderer), _scriptEngine->ExecuteIsOpaqueBlock(renderer))));
}

BlockRenderers::BlockRenderers(RendererBase *renderer, const std::string &texturePack) :
	_texturePack(new TexturePackHandler(this, texturePack, renderer)),
	_fonts(this)
{
	if (renderer == nullptr)
		return;

	renderer->Renderers = this;

	int unknownTexture[16 * 16];

	for (int y = 0; y < 16; ++y)
		for (int x = 0; x < 16; ++x)
		{
			if ((x < 8 && y < 8) ||
				(x >= 8 && y >= 8))
				unknownTexture[(y * 16) + x] = 0xAA333333;
			else
				unknownTexture[(y * 16) + x] = 0xAA111111;
		}

	_unknownTex = renderer->CreateTexture();
	_unknownTex->Load((byte*)&unknownTexture[0], 16, 16);

	AddTexture("__unknown__", _unknownTex);

	_fonts.LoadGlyphSizes(_texturePack->LoadFile("font\\glyph_sizes.bin"));

	_scriptEngine = new RenderScriptEngine(this);
	_scriptEngine->BuildScripts(renderer);
}

BlockRenderers::~BlockRenderers()
{
	for (auto it = _texturePool.begin(); it != _texturePool.end(); ++it)
	{
		(*it).second->Delete();
		delete (*it).second;
	}

	delete _texturePack;

	for (auto it = Renderers.begin(); it != Renderers.end(); ++it)
		_scriptEngine->Release((*it).second.Data);

	delete _scriptEngine;
}

#include <gl\glew.h>
#include <gl\GL.h>

void BlockRenderers::SetTexturePack(const wxString &string)
{
	_texturePack->SetPack(string);

	for (auto it = _texturePool.begin(); it != _texturePool.end(); ++it)
	{
		auto &v = *it;
		
		if (!v.first.StartsWith("_") && v.first.Contains("."))
			_texturePack->CreateScaledTexture(v.first);
	}

	_fonts.LoadGlyphSizes(_texturePack->LoadFile("font\\glyph_sizes.bin"));
}

void BlockRenderers::ExecuteRenderer(Schematic *schematic, PrimitiveRendererBase *renderer, const Block &block, const Point3i &position)
{
	auto it = Renderers.find(block.ID);

	if (it != Renderers.end())
		_scriptEngine->Execute(schematic, renderer, block, position, (*it).second.Data);
	else
		renderer->Draw(Box(_unknownTex));
}

bool BlockRenderers::IsFullBlock(const Block &block)
{
	auto it = Renderers.find(block.ID);

	if (it != Renderers.end())
		return (*it).second.IsFull();

	return false;
}

bool BlockRenderers::IsOpaqueBlock(const Block &block)
{
	auto it = Renderers.find(block.ID);

	if (it != Renderers.end())
		return (*it).second.IsOpaque();

	return false;
}